package icasue.net.socket.share;

import icasue.net.base.annotations.NotNull;
import icasue.net.base.annotations.Nullable;
import icasue.net.socket.model.register.RegisterInfo;
import icasue.net.socket.thread.Acceptor;
import icasue.net.socket.thread.ResourceResolver;
import icasue.net.socket.utils.PoolUtil;
import icasue.net.socket.utils.Refection;
import icasue.net.socket.wareable.IdWorker;
import icasue.net.socket.driver.event.EventCommandDriver;
import icasue.net.socket.driver.mdc.MDCommandDriver;
import icasue.net.socket.driver.providers.CommandDriverManager;
import icasue.net.socket.driver.providers.EventCommandDriverProvider;
import icasue.net.socket.driver.providers.MDCommandDriverProvider;
import icasue.net.socket.utils.Cast;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/3/26 上午11:32
 * @UpdateDate:
 * @Description:
 */
public final class ServerRunner {

    private ServerSocket serverSocket;

    /*ServerSocket服务端与客户端间通信的tcp端口，默认4477*/
    private Integer tcpPort = 4477;

    /*ServerSocket服务端间通信的tcp端口，默认4478*/
    private Integer serverTcpPort = 4478;

    /*线程池，系统中所有的子线程均来自用户提供的线程池*/
    private ThreadPoolExecutor threadPool;

    /* 客户端注册信息模型，每个参与组队的客户端均持有一个该模型的实例，
     * 用于存储客户端交互的自身信息，该信息在channel创建/加入时进行第一次注册，
     * 后续过程实际上是客户端通过协议Command来变更自身注册信息，并通知参与组队的其他客户端的过程！*/
    private Class<? extends RegisterInfo> registerInfoModel = RegisterInfo.class;

    /*资源释放配置，cron，keep-alive延时.*/
    private String resourceResolveTriggerCron;
    private Integer resourceResolveDelaySecond;

    /*是否阻塞Runner线程, 默认阻塞.*/
    private Boolean blockRunner = Boolean.TRUE;

    /*IdWorker用于生产业务ID，用于标识用户socket和channel,需要保证业务唯一, 建议使用 SnowflakeId */
    private IdWorker idWorker;

    /*客户端扩展的MDCommandDriverProvider,用于管理客户端扩展的一组MDCommandDriver,
    被管理的MDCommandDriver处理客户端扩展的MDCommand命令，推送EventCommand事件.*/
    private List<MDCommandDriverProvider> mdCommandDriverProviders;

    /*客户端扩展的EventCommandDriverProvider,用于管理客户端扩展的一组EventCommandDriver,
    被管理的EventCommandDriver处理MDCommandDriver发送的EventCommand事件.*/
    private List<EventCommandDriverProvider> eventCommandDriverProviders;

    /*也可直接提供一组MDCommandDriver，Runner会为这一组MDCommandDriver默认分配到
    一个MDCommandDriverProvider, 被CommandDriverManager加载*/
    private MDCommandDriverProvider aggregateMDDriversProvider;

    /*也可直接提供一组EventCommandDriver，Runner会为这一组EventCommandDriver默认分配到
    一个EventCommandDriverProvider, 被CommandDriverManager加载*/
    private EventCommandDriverProvider aggregateEventDriversProvider;


    private ServerRunner(@Nullable Integer clientPort, @Nullable Integer serverPort){
        if(clientPort != null){
            this.tcpPort = clientPort;
        }
        if(serverPort != null){
            this.serverTcpPort = serverPort;
        }
    }

    public static ServerRunner builder(@Nullable Integer clientPort,@Nullable Integer serverPort){
        return new ServerRunner(clientPort,serverPort);
    }

    public ServerRunner registerInfoModel(@Nullable Class<? extends RegisterInfo> registerInfoModel){
        if(registerInfoModel != null){
            this.registerInfoModel = registerInfoModel;
        }
        return this;
    }

    public ServerRunner threadPool(@NotNull ThreadPoolExecutor threadPool){
        this.threadPool = threadPool;
        return this;
    }

    public ServerRunner resourceResolveTriggerCorn(@Nullable String triggerCron){
        if(triggerCron != null){
            this.resourceResolveTriggerCron = triggerCron;
        }
        return this;
    }

    public ServerRunner resourceResolveDelaySecond(@Nullable Integer delaySecond){
        if(delaySecond != null){
            this.resourceResolveDelaySecond = delaySecond;
        }
        return this;
    }

    public ServerRunner addMDDriverProviders(@Nullable MDCommandDriverProvider... mdCommandDriverProviders){
        if(mdCommandDriverProviders != null && mdCommandDriverProviders.length > 0){
            this.mdCommandDriverProviders = Optional.ofNullable(this.mdCommandDriverProviders)
                    .orElseGet(() -> new ArrayList<>(mdCommandDriverProviders.length));
            for (MDCommandDriverProvider mdCommandDriverProvider : mdCommandDriverProviders) {
                if(mdCommandDriverProvider != null){
                    this.mdCommandDriverProviders.add(mdCommandDriverProvider);
                }
            }
        }
        return this;
    }

    public ServerRunner addEventDriverProviders(@Nullable EventCommandDriverProvider... eventCommandDriverProviders){
        if(eventCommandDriverProviders != null && eventCommandDriverProviders.length > 0){
            this.eventCommandDriverProviders = Optional.ofNullable(this.eventCommandDriverProviders)
                    .orElseGet(() -> new ArrayList<>(eventCommandDriverProviders.length));
            for (EventCommandDriverProvider eventCommandDriverProvider : eventCommandDriverProviders) {
                if(eventCommandDriverProvider != null){
                    this.eventCommandDriverProviders.add(eventCommandDriverProvider);
                }
            }
        }
        return this;
    }

    public ServerRunner addMDDrivers(@Nullable MDCommandDriver... mdDrivers){
        this.aggregateMDDriversProvider = Optional.ofNullable(this.aggregateMDDriversProvider)
                .orElseGet(() -> new MDCommandDriverProvider(){{ add(mdDrivers); }});
        return this;
    }

    public ServerRunner addEventDrivers(@Nullable EventCommandDriver... eventDrivers){
        this.aggregateEventDriversProvider = Optional.ofNullable(this.aggregateEventDriversProvider)
                .orElseGet(() -> new EventCommandDriverProvider(){{ add(eventDrivers); }});
        return this;
    }



    public ServerRunner idWorker(@NotNull IdWorker idWorker){
        this.idWorker = idWorker;
        return this;
    }

    private void previousRunner(){
        // init thread pool supplied by developer.
        Refection.fieldSet(PoolUtil.class, "pool", null, threadPool);
        // init idWorker instance.
        Refection.fieldSet(MindManger.class,"idWorker",null,this.idWorker);
        // init client and server's tcp port
        if(this.tcpPort != null){
            Refection.fieldSet(MindManger.class,"tcpPort",null,this.tcpPort);
        }
        if(this.serverTcpPort != null){
            Refection.fieldSet(MindManger.class,"serverTcpPort",null,this.serverTcpPort);
        }
        // filling drivers and providers which extend by developer.
        if(this.aggregateMDDriversProvider != null){
            this.mdCommandDriverProviders = Optional.ofNullable(this.mdCommandDriverProviders)
                    .orElseGet(() -> new ArrayList<>(2));
            this.mdCommandDriverProviders.add(this.aggregateMDDriversProvider);
        }
        if(this.aggregateEventDriversProvider != null){
            this.eventCommandDriverProviders = Optional.ofNullable(this.eventCommandDriverProviders)
                    .orElseGet(() -> new ArrayList<>(2));
            this.eventCommandDriverProviders.add(this.aggregateEventDriversProvider);
        }
        if(this.mdCommandDriverProviders != null){
            Cast.castMulti(
                    Refection.fieldGet(CommandDriverManager.class, "mdCommandDriverProviders", null),
                    MDCommandDriverProvider.class
            ).addAll(this.mdCommandDriverProviders);

        }
        if(this.eventCommandDriverProviders != null){
            Cast.castMulti(
                    Refection.fieldGet(CommandDriverManager.class, "eventCommandDriverProviders", null),
                    EventCommandDriverProvider.class
            ).addAll(this.eventCommandDriverProviders);
        }
    }

    public void run() throws IOException,InterruptedException {
        /*Init tcp, idWorker, threadPool*/
        previousRunner();
        /*ServerSocket Runner Running..*/
        try {
            /*ServerSocket.*/
            serverSocket = new ServerSocket(this.tcpPort);

            /*Acceptor Runner.*/
            Acceptor acceptor = new Acceptor(serverSocket, this.registerInfoModel);
            PoolUtil.execute(acceptor);

            /*ResourceResolver Runner.*/
            ResourceResolver resourceResolver = new ResourceResolver(resourceResolveTriggerCron, resourceResolveDelaySecond);
            PoolUtil.execute(resourceResolver);

            /*Choose if block current thread or not.*/
            if(blockRunner){
                Thread.currentThread().join();
            }
        }catch (Throwable e){
            e.printStackTrace();
            throw e;
        }finally {
            if(serverSocket != null){
                serverSocket.close();
            }
        }
    }


    private static <T> List<T> castMulti(Object list, @NotNull Class<T> type){
        return Objects.isNull(list) || Objects.isNull(type)
                ? null : (List<T>) List.class.cast(list);
    }


}
